package com.xjrh.traintravel.collector.netty.handler;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import com.xjrh.middleware.redis.service.RedisService;
import com.xjrh.traintravel.collector.common.*;
import com.xjrh.traintravel.collector.netty.coder.Encoder;
import com.xjrh.traintravel.collector.netty.coder.command.AgreementCommandEnum;
import com.xjrh.traintravel.collector.netty.coder.command.AgreementConstant;
import com.xjrh.traintravel.collector.netty.coder.command.AgreementStructure;
import com.xjrh.traintravel.collector.netty.command.upgrade.UpgradeCommonApply;
import com.xjrh.traintravel.collector.netty.command.upgrade.UpgradeEquipmentData;
import com.xjrh.traintravel.collector.netty.command.upgrade.UpgradeFileApply;
import com.xjrh.traintravel.collector.netty.command.upgrade.UpgradeVersionApply;
import com.xjrh.traintravel.collector.runner.DataCacheTool;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 升级指令处理类
 *
 * @author snps
 * @create 2022-01-22 9:57
 * @describe
 **/
@Service
@Slf4j
public class UpgradeHandler extends BaseAgreementHandler {

    /**
     * 每次读取文件内容大小
     */
    private static final int READ_BYTE_LENGTH = 1000;

    /**
     * 数据缓存时间（单位：分钟）
     */
    @Value("${project.dataCacheTime}")
    private long dataCacheTime;

    /**
     * 升级文件目录
     */
    @Value("${project.equipmentUpgradeFilePath}")
    private String equipmentUpgradeFilePath;

    @Resource
    private DataCacheTool dataCacheTool;

    @Resource
    private RedisService redisService;


    /**
     * 得到升级文件内容
     * @param equipmentId 设备标识（现在使用：设备的mac地址）
     * @param equipmentType 升级类型（0-主机，1-dsp，2-前置）
     * @param fileName 要升级的文件名
     * @return List<String>
     * @throws Exception
     */
    public List<String> getUpgradeFileContent(String equipmentId, Integer equipmentType, String fileName) throws Exception {
        /* 检查文件是否存在 */
        // log.warn("升级文件路径: {}", equipmentUpgradeFilePath);

        String upgradeFile = equipmentUpgradeFilePath + fileName;
        File file = new File(upgradeFile);
        if(! file.exists()) {
            log.error("要升级的文件【{}】不存在，不能下发升级指令!", upgradeFile);
            return null;
        }
        log.warn("要升级的文件：{}", upgradeFile);

        /* 读取文件内容 */
        RandomAccessFile randomAccessFile = new RandomAccessFile(upgradeFile, "r");
        ReadFileBean readFileBean = new ReadFileBean(upgradeFile, randomAccessFile.length(), READ_BYTE_LENGTH);
        log.warn("要升级的文件大小：{}", randomAccessFile.length());
        log.warn("需要读取文件次数：{}", readFileBean.getNeedReadNumber());
        List<String> lstAgreementData = new ArrayList<>(readFileBean.getNeedReadNumber());
        for (int i=0; i<readFileBean.getNeedReadNumber(); i++) {
            readFileBean = readFilePartContent(randomAccessFile, readFileBean);
            // 得到 升级文件内容，并转成16进制字符串
            String agreementDataHexString = getUpgradeFileContent2HexString(equipmentType, readFileBean);
            lstAgreementData.add(agreementDataHexString);
        }
        randomAccessFile.close();

        return lstAgreementData;
    }

    /**
     *  读取文件部分内容
     * @param randomAccessFile 文件
     * @param readFileBean
     * @return
     * @throws IOException
     */
    private ReadFileBean readFilePartContent(RandomAccessFile randomAccessFile, ReadFileBean readFileBean) throws IOException {
        // 如果文件没有读完，跳过一些内容
        if (readFileBean.getAlreadyReadNumber() != readFileBean.getNeedReadNumber()) {
            randomAccessFile.seek(readFileBean.getStartPos());
        }

        /* 设置本次要读取的字节数 */
        int nowReadByteLength = readFileBean.getReadByteLength();
        // 如果”文件字节总数”小于“要读取的字节数”，设置“要读取的字节数”等于“文件字节总数”
        if (readFileBean.getFileByteLength() < readFileBean.getReadByteLength()) {
            nowReadByteLength = (int) readFileBean.getFileByteLength();
        }
        // 如果“剩余未读取的字节数”小于“要读取的字节数”，设置“要读取的字节数”等于“剩余未读取的字节数”
        if (readFileBean.getNoReadByteLength() < readFileBean.getReadByteLength()) {
            nowReadByteLength = (int) readFileBean.getNoReadByteLength();
        }

        // 读文件内容
        byte[] bytes = new byte[nowReadByteLength];
        int byteRead = randomAccessFile.read(bytes);
        readFileBean.setBytes(bytes);

        // 判断文件是否读完
        if (byteRead == -1) {
            readFileBean.setAlreadyReadNumber(readFileBean.getNeedReadNumber());
            readFileBean.setAlreadyReadByteLength(readFileBean.getFileByteLength());
            readFileBean.setStartPos(-1);
            readFileBean.setEndPos(-1);
            readFileBean.setNoReadByteLength(0);
        } else {
            readFileBean.setAlreadyReadNumber(readFileBean.getAlreadyReadNumber() + 1);
            readFileBean.setAlreadyReadByteLength(readFileBean.getAlreadyReadByteLength() + bytes.length);
            readFileBean.setStartPos(readFileBean.getStartPos() + bytes.length);
            readFileBean.setEndPos(readFileBean.getEndPos() + bytes.length);
            readFileBean.setNoReadByteLength(readFileBean.getFileByteLength() - readFileBean.getAlreadyReadByteLength());
        }

        return readFileBean;
    }

    /**
     * 得到 升级文件内容，并转成16进制字符串
     * @param equipmentType 升级类型（0-主机，1-dsp，2-前置）
     * @param readFileBean
     * @return String (HexString)
     */
    private String getUpgradeFileContent2HexString(int equipmentType, ReadFileBean readFileBean) {
        String fileContent = CodeConvertor.bytes2HexString(readFileBean.getBytes());

        StringBuilder sbuHexData = new StringBuilder();
        // 命令(1)
        sbuHexData.append("01");
        // 内容-升级类型(1)
        sbuHexData.append(CodeConvertor.intToHexString(equipmentType, 1));
        // 内容-升级文件总长度(4)
        sbuHexData.append(CodeConvertor.longToHexString(readFileBean.getFileByteLength(), 4));
        // 内容-总包数(2)
        sbuHexData.append(CodeConvertor.intToHexString(readFileBean.getNeedReadNumber(), 2));
        // 内容-升级包号(2)
        sbuHexData.append(CodeConvertor.intToHexString(readFileBean.getAlreadyReadNumber(), 2));
        // 内容-升级数据长度(2)
        sbuHexData.append(CodeConvertor.intToHexString(readFileBean.getBytes().length, 2));
        // 内容-升级数据内容(N)
        sbuHexData.append(fileContent);
        return sbuHexData.toString();
    }

    /**
     * 得到升级协议结构体
     * @param equipmentId 设备标识
     * @param data 数据
     * @return AgreementStructure
     */
    public AgreementStructure getUpgradeAgreement(String equipmentId, String data) {
        AgreementStructure agreement = new AgreementStructure();
        // 前导码(4)
        agreement.setPc(AgreementConstant.PC_VALUE);
        // 帧头(1)
        agreement.setSd(AgreementConstant.SD_VALUE);
        // 控制(1)
        agreement.setFc(AgreementCommandEnum.S_09.getValue());
        // MAC地址(6)
        agreement.setAddr(equipmentId);
        // 序号(1)
        // agreement.setSeq(CodeConvertor.intToHexString(1, 1));
        agreement.setSeq(super.getEquipmentSeq(agreement.getAddr()));
        // 长度(2)
        agreement.setLen(CodeConvertor.intToHexString(data.length()/2, 2));
        // 数据(N)
        agreement.setData(data);
        // 校验(2)
        String crcValue = CRC16M.calculateCrc(agreement);
        agreement.setFcs(crcValue);
        // 帧尾(1)
        agreement.setFs(AgreementConstant.FS_VALUE);

        return agreement;
    }

    /**
     * 从缓存中获取要升级的信息
     * @param upgradeFileContentCacheKey 缓存-升级文件内容-Key
     * @param upgradeFileRateCacheKey 缓存-升级文件进度 Key
     * @return String
     */
    public String getUpgradeFileContentFromCache(String upgradeFileContentCacheKey, String upgradeFileRateCacheKey) {
        // 获取要升级的文件内容
        List<String> lstUpgradeFileContent = dataCacheTool.getUpgradeFileContentCache(upgradeFileContentCacheKey);
        if (lstUpgradeFileContent == null || lstUpgradeFileContent.size() ==0) {
            return null;
        }

        // 获取要升级的文件进度
        Integer upgradeFileRate = dataCacheTool.getUpgradeFileRateCache(upgradeFileRateCacheKey);
        if (upgradeFileRate >= lstUpgradeFileContent.size()) {
            return null;
        }

        return lstUpgradeFileContent.get(upgradeFileRate);
    }

    /**
     * 得到 升级文件-命令响应
     * @param equipmentId 设备标识
     * @param applyData 响应数据
     * @return UpgradeFileApply
     */
    public UpgradeFileApply getUpgradeFileApply(String equipmentId, String applyData) {
        UpgradeFileApply upgradeFileApply = new UpgradeFileApply();
        upgradeFileApply.setEquipmentId(equipmentId);
        upgradeFileApply.setCommonNo(applyData.substring(0, 2));
        upgradeFileApply.setType(Integer.parseInt(applyData.substring(2, 4), 16));
        upgradeFileApply.setResult(Integer.parseInt(applyData.substring(4, 6), 16));
        upgradeFileApply.setUpgradeEndFlag(Integer.parseInt(applyData.substring(6), 16));
        return upgradeFileApply;
    }

    /**
     * 发送升级文件数据
     * @param agreementStructure
     * @param channel
     */
    public void sendUpgradeFileData(AgreementStructure agreementStructure, Channel channel) {
        // 转16进制发送
        String hexAgreementString = Encoder.encode(agreementStructure);

        ByteBuf buff = Unpooled.buffer();
        buff.writeBytes(CodeConvertor.hexString2Bytes(hexAgreementString));
        channel.writeAndFlush(buff);
    }


    /************************************************************
     * 下发-获取上一个主机程序版本
     *
     * @param equipmentId equipmentId 设备标识（现在使用：设备的mac地址）
     */
    public void getPreviousVersion(String equipmentId) {
        AgreementStructure agreement = new AgreementStructure();

        // 前导码(4)
        agreement.setPc(AgreementConstant.PC_VALUE);
        // 帧头(1)
        agreement.setSd(AgreementConstant.SD_VALUE);
        // 控制(1)
        agreement.setFc(AgreementCommandEnum.S_09.getValue());
        // MAC地址(6)
        agreement.setAddr(equipmentId);
        // 序号(1)
        agreement.setSeq(CodeConvertor.intToHexString(1, 1));
        // 长度(2)
        agreement.setLen(CodeConvertor.intToHexString(1, 2));
        // 数据(N)
        agreement.setData("02");
        // 校验(2)
        String crcValue = CRC16M.calculateCrc(agreement);
        agreement.setFcs(crcValue);
        // 帧尾(1)
        agreement.setFs(AgreementConstant.FS_VALUE);

        // 发送命令
        super.sendCommand(agreement);
    }

    /**
     * 下发-重启
     *
     * @param equipmentId equipmentId 设备标识（现在使用：设备的mac地址）
     */
    public void restart(String equipmentId) {
        // 构造指令
        AgreementStructure agreement = new AgreementStructure();

        // 前导码(4)
        agreement.setPc(AgreementConstant.PC_VALUE);
        // 帧头(1)
        agreement.setSd(AgreementConstant.SD_VALUE);
        // 控制(1)
        agreement.setFc(AgreementCommandEnum.S_09.getValue());
        // MAC地址(6)
        agreement.setAddr(equipmentId);
        // 序号(1)
        agreement.setSeq(CodeConvertor.intToHexString(1, 1));
        // 长度(2)
        agreement.setLen(CodeConvertor.intToHexString(1, 2));
        // 数据(N)
        agreement.setData("03");
        // 校验(2)
        String crcValue = CRC16M.calculateCrc(agreement);
        agreement.setFcs(crcValue);
        // 帧尾(1)
        agreement.setFs(AgreementConstant.FS_VALUE);

        // 发送命令
        super.sendCommand(agreement);
    }

    /**
     * 下发-执行设备升级
     * @param data 设备升级数据
     */
    public void equipmentUpgrade(UpgradeEquipmentData data) {
        AgreementStructure agreement = new AgreementStructure();

        // 构造 数据
        StringBuilder sbuData = new StringBuilder();
        sbuData.append("04");
        sbuData.append(CodeConvertor.intToHexString(data.getEquipmentType(), 1));
        sbuData.append(CodeConvertor.intToHexString(data.getFrontType(), 1));
        sbuData.append(CodeConvertor.intToHexString(data.getRs485(), 1));

        // 前导码(4)
        agreement.setPc(AgreementConstant.PC_VALUE);
        // 帧头(1)
        agreement.setSd(AgreementConstant.SD_VALUE);
        // 控制(1)
        agreement.setFc(AgreementCommandEnum.S_09.getValue());
        // MAC地址(6)
        agreement.setAddr(data.getEquipmentId());
        // 序号(1)
        agreement.setSeq(CodeConvertor.intToHexString(1, 1));
        // 长度(2)
        agreement.setLen(CodeConvertor.intToHexString(4, 2));
        // 数据(N)
        agreement.setData(sbuData.toString());
        // 校验(2)
        String crcValue = CRC16M.calculateCrc(agreement);
        agreement.setFcs(crcValue);
        // 帧尾(1)
        agreement.setFs(AgreementConstant.FS_VALUE);

        // 发送命令
        super.sendCommand(agreement);
    }

    /**
     * 得到 上一个主机程序版本
     * @param equipmentId
     * @param applyData
     * @return
     */
    public UpgradeVersionApply getPreviousVersionApply(String equipmentId, String applyData) {
        UpgradeVersionApply version = new UpgradeVersionApply();
        version.setEquipmentId(equipmentId);
        version.setCommonNo(applyData.substring(0, 2));
        version.setV1(Integer.parseUnsignedInt(applyData.substring(2, 4)));
        version.setV1(Integer.parseUnsignedInt(applyData.substring(4, 6)));
        return version;
    }


    /**
     * 得到 升级通用-命令响应
     * @param equipmentId 设备标识
     * @param applyData 响应数据
     * @return UpgradeCommonApply
     */
    public UpgradeCommonApply getUpgradeCommonApply(String equipmentId, String applyData) {
        UpgradeCommonApply upgradeCommonApply = new UpgradeCommonApply();
        upgradeCommonApply.setEquipmentId(equipmentId);
        upgradeCommonApply.setCommonNo(applyData.substring(0, 2));
        upgradeCommonApply.setResult(Integer.parseInt(applyData.substring(2), 16));
        return upgradeCommonApply;
    }
}